/*
 * Copyright (C) 2015 Daniel Uppenkamp
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.maurersmarbles.states;

import com.google.common.base.Preconditions;
import com.googlecode.maurersmarbles.Card;
import com.googlecode.maurersmarbles.CardDeck;
import com.googlecode.maurersmarbles.GameLogic;
import com.googlecode.maurersmarbles.GameState;
import com.googlecode.maurersmarbles.InputEvent;
import com.googlecode.maurersmarbles.RGB;
import com.googlecode.maurersmarbles.Rectangle;
import com.googlecode.maurersmarbles.Screen;
import com.googlecode.maurersmarbles.TouchEvent;
import com.googlecode.maurersmarbles.drawables.CardDrawable;
import com.googlecode.maurersmarbles.drawables.Drawable;
import com.googlecode.maurersmarbles.drawables.RectangleDrawable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * @author Daniel Uppenkamp
 */
public class DetermineDealerState implements State {

    /**
     * The DetermineDealerState instance.
     */
    private static final DetermineDealerState INSTANCE = new DetermineDealerState();
    /**
     * The ratio of the screen width to the card width.
     */
    protected static final float SCREEN_SIZE_TO_CARD_SIZE = 0.25f;
    /**
     * The animation time (in millis) to deal a card.
     */
    protected static final long ANIMATION_TIME_DEAL_CARD = 250L;
    /**
     * The lock that is used to control access to the player card map.
     */
    protected static final Object PLAYER_CARD_MAP_LOCK = new Object();
    /**
     * Whether or not the cards are still being dealt.
     */
    protected volatile boolean dealingCards = false;
    /**
     * Whether or not to animate the cards being dealt.
     */
    protected volatile boolean skipAnimation = false;
    /**
     * The map that contains the card for each player.
     */
    protected Map<Integer, Card> playerCardMap = new ConcurrentHashMap();

    /**
     * Creates a new DetermineDealerState.
     */
    private DetermineDealerState() {
        // do nothing
    }

    /**
     * Animates the given card being dealt to the given player.
     *
     * @param screen the screen
     * @param playerCardMap the player card map
     * @param card the card to deal
     * @param playerNumber the player number
     * @param duration the duration of the animation (in millis)
     */
    protected void animateCardBeingDelt(Screen screen, Map<Integer, Card> playerCardMap, Card card, int playerNumber, long duration) {

        Preconditions.checkNotNull(screen, "Null screen");
        Preconditions.checkNotNull(playerCardMap, "Null player card map");
        Preconditions.checkNotNull(card, "Null card");

        Long currentTime;
        Long startTime = currentTime = System.currentTimeMillis();

        while (currentTime < (startTime + duration)) {

            Collection<Drawable> drawables = getDrawables(screen, playerCardMap);

            Rectangle cardDeckRectangle = getCardDeckRectangle(screen);
            Rectangle playerCardRectangle = getPlayerCardRectangle(screen, playerNumber);

            double weight = (double) (currentTime - startTime) / (double) duration;

            Rectangle rectangle = Rectangle.interpolate(cardDeckRectangle, playerCardRectangle, weight);
            drawables.add(new CardDrawable(card, rectangle));
            screen.draw(drawables);

            currentTime = System.currentTimeMillis();

        }

        synchronized (PLAYER_CARD_MAP_LOCK) {
            playerCardMap.put(playerNumber, card);
        }

        screen.draw(getDrawables(screen, playerCardMap));

    }

    /**
     * Returns the card deck rectangle based on the screen width and height.
     *
     * @param screen the screen
     * @return the card deck rectangle based on the screen width and height
     */
    protected Rectangle getCardDeckRectangle(Screen screen) {

        Rectangle cardSize = getCardSize(screen);

        int x = (int) ((screen.getWidth() / 2.0) - (cardSize.getWidth() / 2.0));
        int y = (int) ((screen.getHeight() / 2.0) - (cardSize.getHeight() / 2.0));

        return new Rectangle(x, y, cardSize.getWidth(), cardSize.getHeight());

    }

    /**
     * Returns the card size based the screen width and height.
     *
     * @param screen the screen
     * @return the card size based on the screen width and height
     */
    protected Rectangle getCardSize(Screen screen) {

        Preconditions.checkNotNull(screen, "Null screen");

        double height;
        double width;

        if (screen.getHeight() > screen.getWidth()) {
            // portrait
            width = screen.getWidth() * SCREEN_SIZE_TO_CARD_SIZE;
            height = width / Card.WIDTH_TO_HEIGHT_RATIO;
        }
        else {
            // landscape
            height = screen.getHeight() * SCREEN_SIZE_TO_CARD_SIZE;
            width = height * Card.WIDTH_TO_HEIGHT_RATIO;
        }

        return new Rectangle((int) width, (int) height);

    }

    /**
     * Returns the drawables to draw on the screen.
     *
     * @param screen the screen
     * @param playerCardMap the player cards map
     * @return the drawables to draw on the screen
     */
    protected Collection<Drawable> getDrawables(Screen screen, Map<Integer, Card> playerCardMap) {

        Preconditions.checkNotNull(playerCardMap, "Null player card map");

        Collection<Drawable> drawables = new ArrayList();

        drawables.add(new RectangleDrawable(0, 0, screen.getWidth(), screen.getHeight(), RGB.WHITE, true));
        drawables.add(new CardDrawable(null, getCardDeckRectangle(screen)));

        synchronized (PLAYER_CARD_MAP_LOCK) {

            for (int i = 0; i < GameLogic.NUMBER_OF_PLAYERS; i++) {
                if (playerCardMap.containsKey(i)) {
                    drawables.add(new CardDrawable(playerCardMap.get(i), getPlayerCardRectangle(screen, i)));
                }
            }

        }

        return drawables;

    }

    /**
     * Returns the DetermineDealerState instance.
     *
     * @return the DetermineDealerState instance
     */
    public static DetermineDealerState getInstance() {
        return INSTANCE;
    }

    /**
     * Returns the player card rectangle based on the screen width and height.
     *
     * @param screen the screen
     * @param playerNumber the player number
     * @return the player card rectangle based on the screen width and height
     */
    protected Rectangle getPlayerCardRectangle(Screen screen, int playerNumber) {

        Preconditions.checkNotNull(screen, "Null screen");

        Rectangle cardDeckRectangle = getCardDeckRectangle(screen);
        Rectangle cardSize = getCardSize(screen);

        int spacing;
        if (screen.getHeight() > screen.getWidth()) {
            // portrait
            spacing = (int) ((cardDeckRectangle.getX() / 2.0) - (cardSize.getWidth() / 2.0));
        }
        else {
            // landscape
            spacing = (int) ((cardDeckRectangle.getY() / 2.0) - (cardSize.getHeight() / 2.0));
        }

        int x = 0;
        int y = 0;

        switch (playerNumber) {

            case 0:
                // player 0 is on the bottom
                x = cardDeckRectangle.getX();
                y = cardDeckRectangle.getBottom() + spacing;
                break;

            case 1:
                // player 1 is on the left
                x = cardDeckRectangle.getX() - cardSize.getWidth() - spacing;
                y = cardDeckRectangle.getY();
                break;

            case 2:
                // player 2 is on the top
                x = cardDeckRectangle.getX();
                y = cardDeckRectangle.getY() - cardSize.getHeight() - spacing;
                break;

            case 3:
                // player 3 is on the right
                x = cardDeckRectangle.getRight() + spacing;
                y = cardDeckRectangle.getY();
                break;

        }

        return new Rectangle(x, y, cardSize.getWidth(), cardSize.getHeight());

    }

    @Override
    public State enter(Screen screen, GameState gameState) {

        Preconditions.checkNotNull(gameState, "Null game state");
        Preconditions.checkNotNull(playerCardMap, "Null player card map");

        // set the flag to indicate we are dealing cards
        skipAnimation = false;
        dealingCards = true;

        // clear the previous player cards
        synchronized (PLAYER_CARD_MAP_LOCK) {
            playerCardMap.clear();
        }

        // check to see if we have a screen
        if (screen != null) {
            screen.draw(getDrawables(screen, playerCardMap));
        }

        // player 0 will deal the cards to see who the first dealer is
        int dealer = 0;

        // create a new shuffled card deck
        CardDeck cardDeck = new CardDeck(true);

        // deal cards face up until an Ace is dealt
        while (!cardDeck.isEmpty()) {

            Card card = cardDeck.nextCard();

            if (screen != null && !skipAnimation) {
                animateCardBeingDelt(screen, playerCardMap, card, dealer, ANIMATION_TIME_DEAL_CARD);
            }
            else {
                synchronized (PLAYER_CARD_MAP_LOCK) {
                    playerCardMap.put(dealer, card);
                }
            }

            if (card.getId() == Card.CARD_ID_A) {
                // player with Ace is the first dealer
                break;
            }

            dealer = GameLogic.getNextPlayerNumber(dealer);

        }

        if (screen != null) {
            // TODO Add something to indicate who the first dealer is
            screen.draw(getDrawables(screen, playerCardMap));
        }

        /**
         * Set the dealer in the game state. The function to deal new cards will
         * automatically advance to the next dealer so we need to set the dealer
         * to the player before the dealer.
         */
        gameState.setDealer(dealer - 1);

        dealingCards = false;

        // if we do not have a screen, advance to the next state
        if (screen == null) {
            // TODO Are we going to use a null screen as an indictor?
            return DealCardsState.getInstance();
        }

        // return null to stay in this state
        return null;

    }

    @Override
    public State handleInputEvent(Screen screen, GameState gameState, InputEvent inputEvent) {

        Preconditions.checkNotNull(inputEvent, "Null input event");

        if (inputEvent instanceof TouchEvent) {

            TouchEvent touchEvent = (TouchEvent) inputEvent;

            if (touchEvent.getType() == TouchEvent.TYPE_RELEASED) {

                // check to see if we are still dealing cards
                if (!dealingCards) {
                    return DealCardsState.getInstance();
                }
                else {
                    skipAnimation = true;
                }

            }

        }

        return null;

    }

    @Override
    public void handleResizeEvent(Screen screen, GameState gameState, int width, int height) {

        /**
         * Check to see if we have a screen and whether or not we are dealing
         * cards. If we are still dealing cards, the screen will be redrawn with
         * the next animation so no need to trigger a redraw here.
         */
        if (screen != null && !dealingCards) {

            // get the drawables and draw them on the screen
            screen.draw(getDrawables(screen, playerCardMap));

        }

    }

}
